package wowcatsim;

import wowcatsim.interfaces.CatStrategy;
import wowcatsim.strategy.DoNothingStrategy;
import wowcatsim.utils.MersenneTwisterFast;

public class SimulatorJob extends Thread {

    public enum Action {

        NONE, RAKE, MANGLE, SHRED, RIP, BITE, ROAR
    };

    public enum AttackResult {

        HIT, MISS, DODGE, GLANCE, CRIT
    };
    private int mangleTimer = 0;
    private int rakeTimer = 0;
    private int roarTimer = 0;
    private int ripTimer = 0;
    private boolean oocUp = false;
    private boolean mangleUp = false;
    private boolean bleedUp = false;
    private int energy = 0;
    private int comboPoints = 0;
    private int rakeCost = 40;
    private int mangleCost = 45;
    private int shredCost = 60;
    private Simulator simulator;
    private CatStrategy strategy = new DoNothingStrategy();
    private CatStatistics catStats = new CatStatistics();
    private MersenneTwisterFast random = new MersenneTwisterFast();
    private SimulatorSettings settings = new SimulatorSettings();
    private int jobId;
    private boolean log = false;

    public class SimStatus {

        private SimStatus() {
        }

        public int getMangleTimer() {
            return mangleTimer;
        }

        public int getRakeTimer() {
            return rakeTimer;
        }

        public int getRipTimer() {
            return ripTimer;
        }

        public int getRoarTimer() {
            return roarTimer;
        }

        public int getComboPoints() {
            return comboPoints;
        }

        public int getEnergy() {
            return energy;
        }

        public boolean isOocUp() {
            return oocUp;
        }

        public boolean isMangleUp() {
            return mangleUp;
        }

        public boolean isBleedUp() {
            return bleedUp;
        }

        public int getRakeCost() {
            return rakeCost;
        }

        public int getMangleCost() {
            return mangleCost;
        }

        public int getShredCost() {
            return shredCost;
        }
    }

    public SimulatorJob(Simulator sim, int jobId) {
        simulator = sim;
        this.jobId = jobId;
        random.setSeed(jobId);
    }

    public void setStrategy(CatStrategy strat) {
        strategy = strat;
    }

    public void setStatistics(CatStatistics stats) {
        catStats = stats;
    }

    public void setSettings(SimulatorSettings s) {
        settings = s;
    }

    public void setLog(boolean b) {
        log = b;
    }

    @Override
    public void run() {
        int ripCP = 0;
        double damage = 0;

        int maxTimer = settings.MaxTimer;
        int startTime = settings.StartTime;

        double nextAutoAttack = startTime;
        int globalCooldownTimer = 0;

        double naturalist = 1.0 + 0.02 * catStats.Naturalist;
        double critDamage = 2.0 + 0.04 * catStats.PredatoryInstincts;
        double dmgMod = 1 - settings.DamageReduction;

        // Talents reducing costs
        rakeCost -= catStats.Ferocity;
        mangleCost -= catStats.Ferocity;
        mangleCost -= 2 * catStats.ImprovedMangle;
        if (settings.T6Bonus)
            mangleCost -= 5;
        shredCost -= 9 * catStats.ShreddingAttacks;

        strategy.setStatus(new SimStatus());

        for (int timer = -300; timer < maxTimer; timer++) {
            double attackPower = catStats.attackPower;
            mangleUp = settings.MangleUp;
            bleedUp = settings.BleedUp;

            // Reset damage counter at time = 0;
            if (timer == 0) {
                damage = 0;
            }
            if (roarTimer > 0) {
                roarTimer--;
                attackPower *= 1.40;
                if (log && roarTimer == 0) {
                    simulator.log(timer + ": Savage Roar fades");
                }
            }

            if (mangleTimer > 0) {
                mangleUp = true;
                mangleTimer--;
            }

            if (rakeTimer > 0) {
                rakeTimer--;
                bleedUp = true;
                if (rakeTimer % 30 == 0) {
                    // Rake tick
                    double rake = naturalist * (0.06 * attackPower + 387) * (1 + catStats.SavageFury * 0.1);
                    damage += (mangleUp ? rake * 1.3 : rake);
                    if (log) {
                        simulator.log(timer + ": (" + rakeTimer + ") Rake tick for " + (mangleUp ? rake : rake * 1.3));
                    }
                }
            }

            if (ripTimer > 0) {
                bleedUp = true;
                ripTimer--;
                if (ripTimer % 20 == 0) {
                    // Rip tick
                    double rip = naturalist * (32 + (72 + 0.01 * attackPower) * ripCP);
                    damage += (mangleUp ? rip * 1.3 : rip);
                    if (log) {
                        simulator.log(timer + ": (" + ripTimer + ") Rip tick for " + (mangleUp ? rip : rip * 1.3));
                    }
                }
            }

            // Handle auto attacks
            if (nextAutoAttack <= timer) {
                AttackResult result = autoAttack();
                switch (result) {
                    case HIT:
                        damage += dmgMod * naturalist * (attackPower + 768) / 14;
                        if (random.nextDouble() < settings.OOC_CHANCE) {
                            oocUp = true;
                            if (log) {
                                simulator.log(timer + ": Omen of Clarity Proced!");
                            }
                        }
                        if (log) {
                            simulator.log(timer + ": Autoattack hit for " + dmgMod * naturalist * (attackPower + 768) / 14);
                        }
                        break;

                    case CRIT:
                        damage += dmgMod * naturalist * critDamage * (attackPower + 768) / 14;
                        if (random.nextDouble() < settings.OOC_CHANCE) {
                            oocUp = true;
                            if (log) {
                                simulator.log(timer + ": Omen of Clarity Proced!");
                            }
                        }
                        if (log) {
                            simulator.log(timer + ": Autoattack CRIT for " + critDamage * dmgMod * naturalist * (attackPower + 768) / 14);
                        }
                        break;

                    case GLANCE:
                        damage += dmgMod * naturalist * settings.GLANCE_MODIFIER * (attackPower + 768) / 14;
                        if (random.nextDouble() < settings.OOC_CHANCE) {
                            oocUp = true;
                            if (log) {
                                simulator.log(timer + ": Omen of Clarity Proced!");
                            }
                        }
                        if (log) {
                            simulator.log(timer + ": Autoattack glance for " + settings.GLANCE_MODIFIER * dmgMod * naturalist * (attackPower + 768) / 14);
                        }
                        break;

                    default:
                        break;
                }

                nextAutoAttack += 1000.0 / (100 + catStats.hastePercent);
            }

            // Call strategy
            if (globalCooldownTimer == 0) {
                AttackResult result = yellowAttack();
                Action action = strategy.getAction();
                switch (action) {
                    case MANGLE:
                        if (energy < mangleCost && !oocUp) {
                            break;
                        }
                        double mangle = dmgMod * naturalist * (1 + catStats.SavageFury * 0.1) * (2 * (attackPower + 768) / 14 + 634);
                        switch (result) {
                            case HIT:
                                damage += mangle;
                                if (!oocUp) {
                                    energy -= mangleCost;
                                }
                                mangleTimer = 180;
                                comboPoints++;
                                if (log) {
                                    simulator.log(timer + " Mangle hit! Dmg = " + mangle);
                                }
                                break;

                            case CRIT:
                                damage += critDamage * mangle;
                                if (!oocUp) {
                                    energy -= mangleCost;
                                }
                                mangleTimer = 180;
                                yellowCrit();
                                if (log) {
                                    simulator.log(timer + " Mangle crit! Dmg = " + critDamage * mangle);
                                }
                                break;

                            default:
                                if (!oocUp) {
                                    energy -= 0.3 * mangleCost;
                                }
                                break;
                        }
                        if (log) {
                            simulator.log("Combo points = " + comboPoints);
                        }
                        oocUp = false;
                        globalCooldownTimer = 10;
                        break;

                    case SHRED:
                        if (energy < shredCost && !oocUp) {
                            break;
                        }
                        double shred = dmgMod * naturalist * (2.25 * (attackPower + 768) / 14 + 742.5);
                        if (mangleUp) {
                            shred *= 1.3;
                        }
                        if (bleedUp) {
                            shred *= 1.0 + 0.04 * catStats.RendAndTear;
                        }
                        switch (result) {
                            case HIT:
                                damage += shred;
                                if (!oocUp) {
                                    energy -= shredCost;
                                }
                                comboPoints++;
                                if (log) {
                                    simulator.log(timer + " : Shred HIT for " + shred + " CP = " + comboPoints);
                                }
                                break;

                            case CRIT:
                                damage += critDamage * shred;
                                if (!oocUp) {
                                    energy -= shredCost;
                                }
                                yellowCrit();
                                if (log) {
                                    simulator.log(timer + " : Shred CRIT for " + shred * critDamage + " CP = " + comboPoints);
                                }
                                break;

                            default:
                                if (!oocUp) {
                                    energy -= 0.3 * mangleCost;
                                }
                                break;
                        }
                        oocUp = false;
                        globalCooldownTimer = 10;
                        break;

                    case RAKE:
                        if (energy < rakeCost) {
                            break;
                        }
                        double rake = dmgMod * naturalist * (190 + attackPower / 100) * (1 + catStats.SavageFury * 0.1);
                        if (mangleUp) {
                            rake *= 1.3;
                        }
                        switch (result) {
                            case HIT:
                                damage += rake;
                                if (!oocUp) {
                                    energy -= rakeCost;
                                }
                                comboPoints++;
                                rakeTimer = 90;
                                if (log) {
                                    simulator.log(timer + " : Rake HIT for " + rake + ", CP = " + comboPoints);
                                }
                                break;

                            case CRIT:
                                damage += critDamage * rake;
                                if (!oocUp) {
                                    energy -= rakeCost;
                                }
                                yellowCrit();
                                rakeTimer = 90;
                                if (log) {
                                    simulator.log(timer + " : Rake CRIT for " + rake * critDamage + ", CP = " + comboPoints);
                                }
                                break;

                            default:
                                if (!oocUp) {
                                    energy -= 0.3 * rakeCost;
                                }
                                break;
                        }
                        oocUp = false;
                        globalCooldownTimer = 10;
                        break;

                    case BITE:
                        if (energy < 35) {
                            break;
                        }
                        switch (result) {
                            case HIT:
                            case CRIT:
                                if (!oocUp) {
                                    energy -= 35;
                                }
                                double bite = (290 + 0.07 * attackPower) * comboPoints + 120 + (9.4 + attackPower / 410) * energy;
                                bite *= (dmgMod * naturalist);
                                bite *= (1.0 + 0.03 * catStats.FeralAggression);

                                // Recalculate crit%
                                double bitecrit = catStats.critPercent / 100;
                                if (bleedUp) {
                                    bitecrit += 0.1 * catStats.RendAndTear;
                                }
                                if (random.nextDouble() < bitecrit) {
                                    bite *= critDamage;
                                    if (log) {
                                        simulator.log(timer + " : Ferocious bite CRITS for " + bite);
                                    }
                                } else {
                                    if (log) {
                                        simulator.log(timer + " : Ferocious bite HITS for " + bite);
                                    }
                                }
                                damage += bite;
                                energy = 0;
                                comboPoints = 0;
                                break;

                            default:
                                if (!oocUp) {
                                    energy -= (1.0 - 0.4 * catStats.PrimalPrecision) * 35;
                                }
                                break;
                        }

                        globalCooldownTimer = 10;
                        break;

                    case RIP:
                        if (energy < 30 && !oocUp) {
                            break;
                        }
                        switch (result) {
                            case HIT:
                            case CRIT:
                                ripCP = comboPoints;
                                comboPoints = 0;
                                ripTimer = 160;
                                if (!oocUp) {
                                    energy -= 30;
                                }
                                if (log) {
                                    simulator.log(timer + ": Rip hits, CP = " + ripCP + ", Energy = " + energy);
                                }
                                break;

                            default:
                                if (!oocUp) {
                                    energy -= (1.0 - 0.4 * catStats.PrimalPrecision) * 30;
                                }
                                break;
                        }
                        oocUp = false;
                        globalCooldownTimer = 10;
                        break;

                    case ROAR:
                        if (energy < 25 && !oocUp) {
                            break;
                        }
                        if (!oocUp) {
                            energy -= 25;
                        } else {
                            oocUp = false;
                        }
                        roarTimer = 90 + comboPoints * 50;
                        if (log) {
                            simulator.log(timer + ": Savage Roar up, CP = " + comboPoints + ", duration = " + roarTimer / 10);
                        }
                        comboPoints = 0;
                        globalCooldownTimer = 10;
                        break;

                    default:
                        break;
                }
            }

            if (comboPoints > 5) {
                comboPoints = 5;
            }
            if (globalCooldownTimer > 0) {
                globalCooldownTimer--;
            }
            energy++;
            if (energy > 100) {
                energy = 100;
            }
        }

        simulator.jobFinished(jobId, damage * 10 / maxTimer);
    }

    private AttackResult autoAttack() {
        final double missChance = settings.MISS_CHANCE > catStats.hitPercent / 100 ? settings.MISS_CHANCE - catStats.hitPercent / 100 : 0;
        final double dodgeChance = settings.DODGE_CHANCE > catStats.expertisePercent / 100 ? settings.DODGE_CHANCE - catStats.expertisePercent / 100 : 0;

        double roll = random.nextDouble();

        if ((roll -= missChance) < 0) {
            return AttackResult.MISS;
        }
        if ((roll -= dodgeChance) < 0) {
            return AttackResult.DODGE;
        }
        if ((roll -= settings.GLANCE_CHANCE) < 0) {
            return AttackResult.GLANCE;
        }
        if ((roll -= catStats.critPercent / 100) < 0) {
            return AttackResult.CRIT;
        }
        return AttackResult.HIT;
    }

    private AttackResult yellowAttack() {
        final double missChance = settings.MISS_CHANCE > catStats.hitPercent / 100 ? settings.MISS_CHANCE - catStats.hitPercent / 100 : 0;
        final double dodgeChance = settings.DODGE_CHANCE > catStats.expertisePercent / 100 ? settings.DODGE_CHANCE - catStats.expertisePercent / 100 : 0;
        final double yellowCrit = catStats.critPercent * (1 - missChance - dodgeChance) / 100;

        double roll = random.nextDouble();

        if ((roll -= missChance) < 0) {
            return AttackResult.MISS;
        }
        if ((roll -= dodgeChance) < 0) {
            return AttackResult.DODGE;
        }
        if ((roll -= yellowCrit) < 0) {
            return AttackResult.CRIT;
        }
        return AttackResult.HIT;
    }

    private void yellowCrit() {
        switch (catStats.PrimalFury) {
            case 0:
                comboPoints++;
                break;

            case 1:
                if (random.nextBoolean()) {
                    comboPoints += 2;
                } else {
                    comboPoints++;
                }
                break;

            case 2:
                comboPoints += 2;
                break;
        }
        if (comboPoints > 5) {
            comboPoints = 5;
        }
    }
}
